/*
 * Copyright (C) 2022-2024, Xiaohua Semiconductor Co., Ltd.
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 * Change Logs:
 * Date           Author       Notes
 * 2022-04-28     CDT          first version
 */

/*******************************************************************************
 * Include files
 ******************************************************************************/
#include "drv_eth.h"

#if defined(BSP_USING_ETH)

#include <netif/ethernetif.h>
#include <lwipopts.h>
#include "drv_irq.h"
#include "board_config.h"

/*******************************************************************************
 * Local pre-processor symbols/macros ('#define')
 ******************************************************************************/
//#define DRV_DEBUG
#define LOG_TAG                         "drv.eth"
#include <drv_log.h>

#define MAX_ADDR_LEN                    6

/*******************************************************************************
 * Local type definitions ('typedef')
 ******************************************************************************/
struct phy_reg
{
    uint16_t sr;            /*!< PHY status register Offset           */
    uint16_t speed_mask;    /*!< PHY 100M Speed mask                  */
    uint16_t duplex_mask;   /*!< PHY Full-Duplex mask                 */
};

struct hc32_eth
{
    /* inherit from ethernet device */
    struct eth_device       parent;
#if !(defined(ETH_PHY_USING_INTERRUPT_MODE))
    rt_timer_t              poll_link_timer;
#endif
    /* PHY reg val */
    struct phy_reg          eth_phy;
    /* eth irq */
    struct hc32_irq_config  irq_config;
    func_ptr_t              irq_callback;
};

/* eth phy status */
enum
{
    ETH_PHY_LINK        = 0x01U,
    ETH_PHY_100M        = 0x02U,
    ETH_PHY_FULL_DUPLEX = 0x04U,
};

/*******************************************************************************
 * Global variable definitions (declared in header file with 'extern')
 ******************************************************************************/
extern rt_err_t rt_hw_eth_board_init(CM_ETH_TypeDef *CM_ETHx);
extern rt_err_t rt_hw_eth_phy_reset(CM_ETH_TypeDef *CM_ETHx);

/*******************************************************************************
 * Local function prototypes ('static')
 ******************************************************************************/
static void eth_global_irq_handle(void);

/*******************************************************************************
 * Local variable definitions ('static')
 ******************************************************************************/
static stc_eth_handle_t EthHandle;
/* Ethernet Tx DMA Descriptor */
__ALIGN_BEGIN static stc_eth_dma_desc_t EthDmaTxDscrTab[ETH_TX_BUF_NUM];
/* Ethernet Rx DMA Descriptor */
__ALIGN_BEGIN static stc_eth_dma_desc_t EthDmaRxDscrTab[ETH_RX_BUF_NUM];
/* Ethernet Transmit Buffer */
__ALIGN_BEGIN static uint8_t EthTxBuff[ETH_TX_BUF_NUM][ETH_TX_BUF_SIZE];
/* Ethernet Receive Buffer */
__ALIGN_BEGIN static uint8_t EthRxBuff[ETH_RX_BUF_NUM][ETH_RX_BUF_SIZE];
static struct hc32_eth hc32_eth_device =
{
    .irq_config     = ETH_IRQ_CONFIG,
    .irq_callback   = eth_global_irq_handle,
};

/*******************************************************************************
 * Function implementation - global ('extern') and local ('static')
 ******************************************************************************/
static rt_err_t rt_hc32_eth_init(rt_device_t dev)
{
    uint16_t u16RegVal;

    rt_hw_eth_phy_reset(CM_ETH);

    (void)ETH_PHY_ReadReg(ETH_PHY_ADDR, PHY_ID2_REG, &u16RegVal);
    switch ((u16RegVal >> 4) & 0x3F)
    {
        case 0x02: //JL11x1
            hc32_eth_device.eth_phy.sr          = 0x00U;
            hc32_eth_device.eth_phy.speed_mask  = 0x2000U;
            hc32_eth_device.eth_phy.duplex_mask = 0x0100U;
            break;
        case 0x12: //YT8512
            hc32_eth_device.eth_phy.sr          = 0x11U;
            hc32_eth_device.eth_phy.speed_mask  = 0x4000U;
            hc32_eth_device.eth_phy.duplex_mask = 0x2000U;
            break;
        case 0x16: //KSZ8081
            hc32_eth_device.eth_phy.sr          = 0x1EU;
            hc32_eth_device.eth_phy.speed_mask  = 0x0002U;
            hc32_eth_device.eth_phy.duplex_mask = 0x0004U;
            break;
        default:
            return -RT_ERROR;
    }

    return RT_EOK;
}

static rt_err_t rt_hc32_eth_open(rt_device_t dev, rt_uint16_t oflag)
{
    LOG_D("eth open");
    return RT_EOK;
}

static rt_err_t rt_hc32_eth_close(rt_device_t dev)
{
    LOG_D("eth close");
    return RT_EOK;
}

static rt_ssize_t rt_hc32_eth_read(rt_device_t dev, rt_off_t pos, void *buffer, rt_size_t size)
{
    LOG_D("eth read");
    rt_set_errno(-RT_ENOSYS);
    return 0;
}

static rt_ssize_t rt_hc32_eth_write(rt_device_t dev, rt_off_t pos, const void *buffer, rt_size_t size)
{
    LOG_D("eth write");
    rt_set_errno(-RT_ENOSYS);
    return 0;
}

static rt_err_t rt_hc32_eth_control(rt_device_t dev, int cmd, void *args)
{
    switch (cmd)
    {
    case NIOCTL_GADDR:
        /* get mac address */
        if (args)
        {
            SMEMCPY(args, EthHandle.stcCommInit.au8MacAddr, MAX_ADDR_LEN);
        }
        else
        {
            return -RT_ERROR;
        }
        break;

    default :
        break;
    }

    return RT_EOK;
}

#ifdef RT_USING_DEVICE_OPS
const static struct rt_device_ops eth_ops =
{
    rt_hc32_eth_init,
    rt_hc32_eth_open,
    rt_hc32_eth_close,
    rt_hc32_eth_read,
    rt_hc32_eth_write,
    rt_hc32_eth_control
};
#endif

/* ethernet device interface */
/* transmit data*/
rt_err_t rt_hc32_eth_tx(rt_device_t dev, struct pbuf *p)
{
    rt_err_t errval = -RT_ERROR;
    struct pbuf *q;
    uint8_t *txBuffer;
    __IO stc_eth_dma_desc_t *DmaTxDesc;
    uint32_t byteCnt;
    uint32_t frameLength = 0UL;
    uint32_t bufferOffset;
    uint32_t payloadOffset;

    DmaTxDesc = EthHandle.stcTxDesc;
    txBuffer = (uint8_t *)((EthHandle.stcTxDesc)->u32Buf1Addr);
    bufferOffset = 0UL;
    /* Copy frame from pbufs to driver buffers */
    for (q = p; q != NULL; q = q->next)
    {
        /* If this buffer isn't available, goto error */
        if (0UL != (DmaTxDesc->u32ControlStatus & ETH_DMA_TXDESC_OWN))
        {
            LOG_D("buffer not valid");
            errval = (err_t)ERR_USE;
            goto error;
        }

        /* Get bytes in current lwIP buffer */
        byteCnt = q->len;
        payloadOffset = 0UL;
        /* Check if the length of data to copy is bigger than Tx buffer size */
        while ((byteCnt + bufferOffset) > ETH_TX_BUF_SIZE)
        {
            /* Copy data to Tx buffer*/
            SMEMCPY((uint8_t *) & (txBuffer[bufferOffset]), (uint8_t *) & (((uint8_t *)q->payload)[payloadOffset]), (ETH_TX_BUF_SIZE - bufferOffset));
            /* Point to next descriptor */
            DmaTxDesc = (stc_eth_dma_desc_t *)(DmaTxDesc->u32Buf2NextDescAddr);
            /* Check if the buffer is available */
            if (0UL != (DmaTxDesc->u32ControlStatus & ETH_DMA_TXDESC_OWN))
            {
                errval = (err_t)ERR_USE;
                goto error;
            }

            txBuffer = (uint8_t *)(DmaTxDesc->u32Buf1Addr);
            byteCnt = byteCnt - (ETH_TX_BUF_SIZE - bufferOffset);
            payloadOffset = payloadOffset + (ETH_TX_BUF_SIZE - bufferOffset);
            frameLength = frameLength + (ETH_TX_BUF_SIZE - bufferOffset);
            bufferOffset = 0UL;
        }
        /* Copy the remaining bytes */
        SMEMCPY((uint8_t *) & (txBuffer[bufferOffset]), (uint8_t *) & (((uint8_t *)q->payload)[payloadOffset]), byteCnt);
        bufferOffset = bufferOffset + byteCnt;
        frameLength = frameLength + byteCnt;
    }
    LOG_D("transmit frame length :%d", frameLength);
    /* Prepare transmit descriptors to give to DMA */
    (void)ETH_DMA_SetTransFrame(&EthHandle, frameLength);
    errval = (err_t)ERR_OK;

error:
    /* When Transmit Underflow flag is set, clear it and issue a Transmit Poll Demand to resume transmission */
    if (RESET != ETH_DMA_GetStatus(ETH_DMA_FLAG_UNS))
    {
        /* Clear DMA UNS flag */
        ETH_DMA_ClearStatus(ETH_DMA_FLAG_UNS);
        /* Resume DMA transmission */
        WRITE_REG32(CM_ETH->DMA_TXPOLLR, 0UL);
    }

    return errval;
}

/* receive data*/
struct pbuf *rt_hc32_eth_rx(rt_device_t dev)
{
    struct pbuf *p = NULL;
    struct pbuf *q;
    uint32_t len;
    uint8_t *rxBuffer;
    __IO stc_eth_dma_desc_t *DmaRxDesc;
    uint32_t byteCnt;
    uint32_t bufferOffset;
    uint32_t payloadOffset;
    uint32_t i;

    /* Get received frame */
    if (LL_OK != ETH_DMA_GetReceiveFrame_Int(&EthHandle))
    {
        LOG_D("receive frame faild");
        return NULL;
    }

    /* Obtain the size of the packet */
    len = (EthHandle.stcRxFrame).u32Len;
    rxBuffer = (uint8_t *)(EthHandle.stcRxFrame).u32Buf;
    LOG_D("receive frame len : %d", len);
    if (len > 0UL)
    {
        /* Allocate a pbuf chain of pbufs from the Lwip buffer pool */
        p = pbuf_alloc(PBUF_RAW, (uint16_t)len, PBUF_POOL);
    }
    if (p != NULL)
    {
        DmaRxDesc = (EthHandle.stcRxFrame).pstcFSDesc;
        bufferOffset = 0UL;
        for (q = p; q != NULL; q = q->next)
        {
            byteCnt = q->len;
            payloadOffset = 0UL;
            /* Check if the length of bytes to copy in current pbuf is bigger than Rx buffer size */
            while ((byteCnt + bufferOffset) > ETH_RX_BUF_SIZE)
            {
                /* Copy data to pbuf */
                SMEMCPY((uint8_t *) & (((uint8_t *)q->payload)[payloadOffset]), (uint8_t *) & (rxBuffer[bufferOffset]), (ETH_RX_BUF_SIZE - bufferOffset));
                /* Point to next descriptor */
                DmaRxDesc = (stc_eth_dma_desc_t *)(DmaRxDesc->u32Buf2NextDescAddr);
                rxBuffer = (uint8_t *)(DmaRxDesc->u32Buf1Addr);
                byteCnt = byteCnt - (ETH_RX_BUF_SIZE - bufferOffset);
                payloadOffset = payloadOffset + (ETH_RX_BUF_SIZE - bufferOffset);
                bufferOffset = 0UL;
            }
            /* Copy remaining data in pbuf */
            SMEMCPY((uint8_t *) & (((uint8_t *)q->payload)[payloadOffset]), (uint8_t *) & (rxBuffer[bufferOffset]), byteCnt);
            bufferOffset = bufferOffset + byteCnt;
        }
    }
    /* Release descriptors to DMA */
    DmaRxDesc = (EthHandle.stcRxFrame).pstcFSDesc;
    for (i = 0UL; i < (EthHandle.stcRxFrame).u32SegCount; i++)
    {
        DmaRxDesc->u32ControlStatus |= ETH_DMA_RXDESC_OWN;
        DmaRxDesc = (stc_eth_dma_desc_t *)(DmaRxDesc->u32Buf2NextDescAddr);
    }
    /* Clear Segment_Count */
    (EthHandle.stcRxFrame).u32SegCount = 0UL;

    /* When Rx Buffer unavailable flag is set, clear it and resume reception */
    if (RESET != ETH_DMA_GetStatus(ETH_DMA_FLAG_RUS))
    {
        /* Clear DMA RUS flag */
        ETH_DMA_ClearStatus(ETH_DMA_FLAG_RUS);
        /* Resume DMA reception */
        WRITE_REG32(CM_ETH->DMA_RXPOLLR, 0UL);
    }

    return p;
}

static void hc32_eth_irq_handle(stc_eth_handle_t *eth_handle)
{
    rt_err_t result;

    (void)eth_handle;
    /* Frame received */
    if (RESET != ETH_DMA_GetStatus(ETH_DMA_FLAG_RIS))
    {
        result = eth_device_ready(&(hc32_eth_device.parent));
        if (result != RT_EOK)
        {
            LOG_I("eth rx complete callback err = %d", result);
        }
        /* Clear the Eth DMA Rx IT pending bits */
        ETH_DMA_ClearStatus(ETH_DMA_FLAG_RIS | ETH_DMA_FLAG_NIS);
    }
}

/* interrupt service routine */
static void eth_global_irq_handle(void)
{
    /* enter interrupt */
    rt_interrupt_enter();
    hc32_eth_irq_handle(&EthHandle);
    /* leave interrupt */
    rt_interrupt_leave();
}

static void hc32_phy_link_change(void)
{
    static rt_uint8_t phy_status = 0;
    rt_uint8_t phy_status_new = 0;
    uint16_t u16RegVal = 0U;

    /* Read PHY_BSR */
    (void)ETH_PHY_ReadReg(ETH_PHY_ADDR, PHY_BASIC_STATUS_REG, &u16RegVal);
    LOG_D("phy basic status reg is 0x%X", u16RegVal);
    if ((0x0000U != u16RegVal) && (0xFFFFU != u16RegVal))
    {
        if (u16RegVal & (PHY_AUTONEGO_COMPLETE_MASK | PHY_LINKED_STATUS_MASK))
        {
            phy_status_new |= ETH_PHY_LINK;

            u16RegVal = 0;
            ETH_PHY_ReadReg(ETH_PHY_ADDR, hc32_eth_device.eth_phy.sr, &u16RegVal);
            LOG_D("phy control status reg is 0x%X.", u16RegVal);

            if (0U != (u16RegVal & hc32_eth_device.eth_phy.duplex_mask))
            {
                phy_status_new |= ETH_PHY_FULL_DUPLEX;
            }
            if (0U != (u16RegVal & hc32_eth_device.eth_phy.speed_mask))
            {
                phy_status_new |= ETH_PHY_100M;
            }
        }
    }

    if (phy_status != phy_status_new)
    {
        phy_status = phy_status_new;
        if (phy_status & ETH_PHY_LINK)
        {
            if (phy_status & ETH_PHY_FULL_DUPLEX)
            {
                EthHandle.stcCommInit.u32DuplexMode = ETH_MAC_DUPLEX_MD_FULL;
            }
            else
            {
                EthHandle.stcCommInit.u32DuplexMode = ETH_MAC_DUPLEX_MD_HALF;
            }
            if (phy_status & ETH_PHY_100M)
            {
                EthHandle.stcCommInit.u32Speed = ETH_MAC_SPEED_100M;
            }
            else
            {
                EthHandle.stcCommInit.u32Speed = ETH_MAC_SPEED_10M;
            }
            LOG_I("link up. phy_status: %x.", phy_status);
            ETH_MAC_SetDuplexSpeed(EthHandle.stcCommInit.u32DuplexMode, EthHandle.stcCommInit.u32Speed);
            ETH_Start();
            eth_device_linkchange(&hc32_eth_device.parent, RT_TRUE);
        }
        else
        {
            LOG_E("link down.");
            ETH_Stop();
            eth_device_linkchange(&hc32_eth_device.parent, RT_FALSE);
        }
    }
}

#if defined(ETH_PHY_USING_INTERRUPT_MODE)
static void eth_phy_irq_handler(void *args)
{
    rt_uint16_t status = 0;

    ETH_PHY_ReadReg(&EthHandle, PHY_IISDR, &status);
    LOG_D("phy interrupt status reg is 0x%X", status);
    hc32_phy_link_change();
}
#endif

rt_weak rt_err_t rt_hw_eth_mac_get(uint8_t *mac)
{
    /* 00-80 uid */
    mac[0] = 0x02;
    mac[1] = 0x80;
    /* generate MAC addr from unique ID */
    mac[2] = (rt_uint8_t)(READ_REG32(CM_EFM->UQID1) >> 8U);
    mac[3] = (rt_uint8_t)(READ_REG32(CM_EFM->UQID1) >> 16U);
    mac[4] = (rt_uint8_t)READ_REG32(CM_EFM->UQID2);
    mac[5] = (rt_uint8_t)(READ_REG32(CM_EFM->UQID2) >> 8U);

    return RT_EOK;
}
#ifdef RT_LWIP_DHCP
rt_weak rt_err_t rt_hw_eth_dhcp_set(void)
{
    return RT_EOK;
}
#endif
/* Register the eth device */
static int eth_device_register(void)
{
    rt_err_t state = RT_EOK;

    rt_hw_eth_mac_get(EthHandle.stcCommInit.au8MacAddr);
    /* Config MAC address in ETH MAC0 */
    (void)ETH_MACADDR_SetAddr(ETH_MAC_ADDR_IDX0, EthHandle.stcCommInit.au8MacAddr);

#ifdef RT_USING_DEVICE_OPS
    hc32_eth_device.parent.parent.ops       = &eth_ops;
#else
    hc32_eth_device.parent.parent.init      = rt_hc32_eth_init;
    hc32_eth_device.parent.parent.open      = rt_hc32_eth_open;
    hc32_eth_device.parent.parent.close     = rt_hc32_eth_close;
    hc32_eth_device.parent.parent.read      = rt_hc32_eth_read;
    hc32_eth_device.parent.parent.write     = rt_hc32_eth_write;
    hc32_eth_device.parent.parent.control   = rt_hc32_eth_control;
#endif
    hc32_eth_device.parent.parent.user_data = RT_NULL;
    hc32_eth_device.parent.eth_rx           = rt_hc32_eth_rx;
    hc32_eth_device.parent.eth_tx           = rt_hc32_eth_tx;
    /* register eth device */
    state = eth_device_init(&(hc32_eth_device.parent), "e0");
    if (RT_EOK == state)
    {
        LOG_I("eth device init success.");
        /* start phy monitor */
        hc32_eth_device.poll_link_timer = rt_timer_create("eth_phy_link", (void (*)(void *))hc32_phy_link_change,
                                      NULL, RT_TICK_PER_SECOND, RT_TIMER_FLAG_PERIODIC|RT_TIMER_FLAG_SOFT_TIMER);
        if (hc32_eth_device.poll_link_timer)
            rt_timer_start(hc32_eth_device.poll_link_timer);
    }
    else
    {
        LOG_E("eth device init faild: %d.", state);
        state = -RT_ERROR;
    }
#ifdef RT_LWIP_DHCP
    rt_hw_eth_dhcp_set();
#endif
    return state;
}
INIT_APP_EXPORT(eth_device_register);

int rt_hw_eth_init(void)
{
    stc_eth_init_t stcEthInit;

    /* Enable ETH clock */
    FCG_Fcg1PeriphClockCmd(FCG1_PERIPH_ETHMAC, ENABLE);
    /* Init Ethernet GPIO */
    rt_hw_eth_board_init(CM_ETH);
    /* Reset ETHERNET */
    (void)ETH_DeInit();
    /* Configure structure initialization */
    (void)ETH_CommStructInit(&EthHandle.stcCommInit);
    (void)ETH_StructInit(&stcEthInit);

    EthHandle.stcCommInit.u32DuplexMode = ETH_MAC_DUPLEX_MD_FULL;
    EthHandle.stcCommInit.u32Speed      = ETH_MAC_SPEED_100M;
#ifdef ETH_INTERFACE_USING_RMII
    EthHandle.stcCommInit.u32Interface  = ETH_MAC_IF_RMII;
#else
    EthHandle.stcCommInit.u32Interface  = ETH_MAC_IF_MII;
#endif
    EthHandle.stcCommInit.u32ReceiveMode = ETH_RX_MD_INT;
#ifdef RT_LWIP_USING_HW_CHECKSUM
    EthHandle.stcCommInit.u32ChecksumMode = ETH_MAC_CHECKSUM_MD_HW;
#else
    EthHandle.stcCommInit.u32ChecksumMode = ETH_MAC_CHECKSUM_MD_SW;
#endif
    /* Configure ethernet peripheral */
    while (LL_OK != ETH_Init(&EthHandle, &stcEthInit));
    ETH_MAC_SetMdcClock();
    /* Initialize Tx Descriptors list: Chain Mode */
    (void)ETH_DMA_TxDescListInit(&EthHandle, EthDmaTxDscrTab, &EthTxBuff[0][0], ETH_TX_BUF_NUM);
    /* Initialize Rx Descriptors list: Chain Mode */
    (void)ETH_DMA_RxDescListInit(&EthHandle, EthDmaRxDscrTab, &EthRxBuff[0][0], ETH_RX_BUF_NUM);
    /* Enable ETH interrupt */
    /* register eth handler */
    hc32_install_irq_handler(&hc32_eth_device.irq_config, hc32_eth_device.irq_callback, RT_TRUE);

    return RT_EOK;
}
INIT_BOARD_EXPORT(rt_hw_eth_init);

#endif
